﻿using JESAI.DistributedLock.Internal;
using StackExchange.Redis;
using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace JESAI.DistributedLock.Redis.RedLock;

internal static class RedLockHelper
{
    private static readonly string LockIdPrefix;

    static RedLockHelper()
    {
        using var currentProcess = Process.GetCurrentProcess();
        LockIdPrefix = $"{Environment.MachineName}_{currentProcess.Id}_";
    }

    public static bool HasSufficientSuccesses(int successCount, int databaseCount)
    {
        // 需要多数票
        var threshold = (databaseCount / 2) + 1;
        //  // 理论上，如果我们有足够多的成功次数，这个方法应该返回 true。
        // 但是由于我们的实现方式，我们只期望在刚好足够或不够的情况下调用这个方法。
        Invariant.Require(successCount <= threshold);
        return successCount >= threshold;
    }

    public static bool HasTooManyFailuresOrFaults(int failureOrFaultCount, int databaseCount)
    {
        // 对于奇数个数据库，我们需要多数票才能使成功成为不可能。然而，对于偶数个数据库，达到50%的失败/故障就足以排除获得多数成功的可能性。
        var threshold = (databaseCount / 2) + (databaseCount % 2);
        // 理论上，如果我们有足够多的成功次数，这个方法应该返回 true。
        // 但是由于我们的实现方式，我们只期望在刚好足够或不够的情况下调用这个方法。

        Invariant.Require(failureOrFaultCount <= threshold);
        return failureOrFaultCount >= threshold;
    }

    public static RedisValue CreateLockId() => LockIdPrefix + Guid.NewGuid().ToString("n");

    public static bool ReturnedFalse(Task<bool> task) => task.Status == TaskStatus.RanToCompletion && !task.Result;

    public static void FireAndForgetReleaseUponCompletion(IRedLockReleasableSynchronizationPrimitive primitive, IDatabase database, Task<bool> acquireOrRenewTask)
    {
        if (ReturnedFalse(acquireOrRenewTask)) { return; }

        acquireOrRenewTask.ContinueWith(static async (t, state) =>
            {
                // 如果我们知道失败了，就不要清理
                if (!ReturnedFalse(t))
                {
                    var (primitive, database) = (Tuple<IRedLockReleasableSynchronizationPrimitive, IDatabase>)state;
                    await primitive.ReleaseAsync(database, fireAndForget: true).ConfigureAwait(false);
                }
            },
            state: Tuple.Create(primitive, database)
        );
    }

    public static CommandFlags GetCommandFlags(bool fireAndForget) =>
        CommandFlags.DemandMaster | (fireAndForget ? CommandFlags.FireAndForget : CommandFlags.None);

    public static async Task<bool> AsBooleanTask(this Task<RedisResult> redisResultTask) => (bool)await redisResultTask.ConfigureAwait(false);
}
